//***************************************************************************
//* INTEL CONFIDENTIAL
//*
//* Copyright(c) Intel Corporation (2013-2021)
//*
//*    Project:        PANTHER (Avoton FPGA)
//*    Filename:        tb_avn_fpga.v
//*    Author:            Matthew Deckard
//*    Description:    Top Level Testbench
//*
//***************************************************************************

`timescale 1ns / 1ps


import espi_pkg::*;

module espi_master_bfm
  (
   output reg       espi_clk,
   output reg       espi_cs_n,
   output reg       espi_reset_n,
   inout wire [3:0] espi_io,
   input wire       espi_alert_n
   );

    // ESPI timing specification
    time TCK;   // Clock period
    time TCKH;  // Minimum clock high time
    time TCKL;  // Minimum clock low time
    time TSLCH; // Minimum CS# setup time
    time TCLSH; // Minimum CS# hold time
    time TSHSL; // Minimum CS# de-assertion time
    time TDVCH; // Minimum data in setup time
    time TCHDX; // Minimum data in hold time
    time TCLQZ; // Maximum output disable time during TAR
    time TCLQV; // Maximum output data valid time
    time TCLQX; // Minimum output data hold time
    time TSHQZ; // Maximum output disable time after CS# de-assertion
    time TSLAZ; // Maximum CS# assertion to IO[1] high-Z
    time TSHAA; // Minimum CS# de-assertion to IO[1] assertion
    time TINIT; // Minimum reset# de-assertion to 1st transaction (GET_CONFIG)

    bit             verbose         = 0;
    bit             debug           = 0;
    bit             stop_on_error   = 0;
    
    reg             tb_dataout_en   = 1'b0;
    reg             start_clock     = 1'b0;

    reg [7:0]       tb_byte_in      = 8'h00;        // Byte sampled in
    reg [7:0]       tb_byte_out     = 8'h00;        // Byte to be sent out
    reg [7:0]       tb_response     = 8'h00;        

    reg [3:0]       tb_data_out     = 4'b0000;      // Formatted across pins
    reg [2:0]       tb_bit_ptr      = 3'd7;

    reg [1:0]       tb_io_mode      = 2'b00;
    reg [2:0]       tb_op_freq      = 3'b000;

    reg [31:0]      tb_wr_config    = 32'd0;
    reg [31:0]      tb_rd_config    = 32'd0;

    reg             tb_crc_error    = 0; // Set to insert Command Phase CRC error
    reg [7:0]       tb_crc8         = 8'h00;
    bit             tb_ignore_waits = 0;
    bit             tb_posted       = 1;

    logic           wsclk;
    logic [15:0]    espi_status;
    logic [15:0]    espi_status_prev;


    assign espi_clk = start_clock ? wsclk : 1'b0;

    // ESPI data out MASTER, if enabled, else high-Z
    assign  espi_io =  ( tb_dataout_en )   ?   tb_data_out  :
                       4'bzzzz;

    // Format ESPI data out MASTER
    always @ ( * ) begin
        case ( tb_io_mode )    
          // Single I/O
          2'b00: begin
              tb_data_out[0] <= tb_byte_out[ tb_bit_ptr + 0 ];
              tb_data_out[1] <= 1'b1;
              tb_data_out[2] <= 1'b1;
              tb_data_out[3] <= 1'b1;
          end
          
          // Dual I/O
          2'b01: begin
              tb_data_out[0] <= tb_byte_out[ tb_bit_ptr + 0 ];
              tb_data_out[1] <= tb_byte_out[ tb_bit_ptr + 1 ];
              tb_data_out[2] <= 1'b1;
              tb_data_out[3] <= 1'b1;
          end
          
          // Quad I/O
          2'b10: begin
              tb_data_out[0] <= tb_byte_out[ tb_bit_ptr + 0 ];
              tb_data_out[1] <= tb_byte_out[ tb_bit_ptr + 1 ];
              tb_data_out[2] <= tb_byte_out[ tb_bit_ptr + 2 ];
              tb_data_out[3] <= tb_byte_out[ tb_bit_ptr + 3 ];
          end
          
          // RESERVED
          default: begin
              tb_data_out[0] <= 'x;
              tb_data_out[1] <= 'x;
              tb_data_out[2] <= 'x;
              tb_data_out[3] <= 'x;
          end
        endcase // case ( tb_io_mode )
    end // always @ ( * )

    // Format ESPI data in from Slave
    always @ ( negedge espi_clk ) begin
        if ( !espi_cs_n ) begin
            case ( tb_io_mode )    
              // Single I/O
              2'b00: begin
                  tb_byte_in[ tb_bit_ptr + 0 ]  <= espi_io[1];
              end
              
              // Dual I/O
              2'b01: begin
                  tb_byte_in[ tb_bit_ptr + 0 ]  <= espi_io[0];
                  tb_byte_in[ tb_bit_ptr + 1 ]  <= espi_io[1];
              end
              
              // Quad I/O
              2'b10: begin
                  tb_byte_in[ tb_bit_ptr + 0 ]  <= espi_io[0];
                  tb_byte_in[ tb_bit_ptr + 1 ]  <= espi_io[1];
                  tb_byte_in[ tb_bit_ptr + 2 ]  <= espi_io[2];
                  tb_byte_in[ tb_bit_ptr + 3 ]  <= espi_io[3];
              end
              
              // RESERVED
              default: begin
                  tb_byte_in[ tb_bit_ptr + 0 ]  <= 1'bx;
                  tb_byte_in[ tb_bit_ptr + 1 ]  <= 1'bx;
                  tb_byte_in[ tb_bit_ptr + 2 ]  <= 1'bx;
                  tb_byte_in[ tb_bit_ptr + 3 ]  <= 1'bx;
              end
            endcase // case ( tb_io_mode )
        end // if ( !espi_cs_n )
    end // always @ ( negedge espi_clk )

    always @(tb_op_freq) begin
        set_timing_params();
    end

    initial begin
        tb_op_freq = 0;
        set_timing_params();
        wsclk      = 0;
        #13;
        forever begin
            #( TCK / 2 ) wsclk = ~wsclk;
        end
    end

    task get_config
      (
       input [11:0]      address,
       output reg [31:0] data
       );
        logic [7:0] config_data[];
        
        write_cmd(GET_CONFIGURATION);
        write_get_config_header(address);
        tar_phase();
        read_response();
        if (tb_byte_in == ACCEPT) begin
            config_data = new[4];
            read_data(config_data);
            data[7:0]   <= config_data[0];
            data[15:8]  <= config_data[1];
            data[23:16] <= config_data[2];
            data[31:24] <= config_data[3];
            config_data.delete();
        end
        
        read_status();
    endtask // get_config

    task set_config
      (
       input [11:0] address,
       input [31:0] data
       );
        write_cmd(SET_CONFIGURATION);
        write_set_config_header(address, data);
        tar_phase();
        read_response();
        read_status();
    endtask // set_config

    task get_status
      (
       output [7:0] status[0:1]
       );
        write_cmd(GET_STATUS);
        tar_phase();
        read_response();
        read_status();
        status[0] = espi_status[7:0];
        status[1] = espi_status[15:8];
    endtask // get_status

    task put_memwr
      (
       input [3:0]  tag,
       input [63:0] addr,
       input        addr64,
       input [7:0]  data[]
      );
        logic [7:0] address[];

        write_cmd(PUT_PC);
        address    = new[4*(addr64+1)];
        address[0] = addr[7:0];
        address[1] = addr[15:8];
        address[2] = addr[23:16];
        address[3] = addr[31:24];
        if (addr64) begin
            address[4] = addr[39:32];
            address[5] = addr[47:40];
            address[6] = addr[55:48];
            address[7] = addr[63:56];
        end
        write_memwr_header(tag, address, data);
        write_data(data);
        tar_phase();
        read_response();
        read_status();
        address.delete();
    endtask // put_memwr

    task put_memrd
      (
       output        deferred,
       output [7:0]  cycle_type,
       output [3:0]  response_tag,
       output [11:0] response_len,
       output [7:0]  data[],
       input [3:0]   tag,
       input [11:0]  len,
       input [63:0]  addr,
       input         addr64
      );
        logic [7:0] address[];
        
        write_cmd(PUT_NP);
        address    = new[4*(addr64+1)];
        address[0] = addr[7:0];
        address[1] = addr[15:8];
        address[2] = addr[23:16];
        address[3] = addr[31:24];
        if (addr64) begin
            address[4] = addr[39:32];
            address[5] = addr[47:40];
            address[6] = addr[55:48];
            address[7] = addr[63:56];
        end
        write_memrd_header(tag, len, address);
        tar_phase();
        read_response();
        deferred = (tb_byte_in == DEFER);
        if (tb_byte_in == ACCEPT) begin
            read_memrd_completion(cycle_type, response_tag, response_len, data);
        end
        read_status();
    endtask // put_memrd

    task put_memwr_short
      (
       input [31:0] addr,
       input [7:0]  data[]
       );
        logic [1:0] len;

        len = data.size()-1;
        write_cmd({PUT_MEMWR32_SHORT, len});
        write_byte(addr[31:24]);
        write_byte(addr[23:16]);
        write_byte(addr[15:8]);
        write_byte(addr[7:0]);
        write_data(data);
        if (debug) begin
            $display("%m(%t): ADDR = %08x", $time, addr);
        end
        tar_phase();
        read_response();
        read_status();
    endtask // put_memwr_short
    
    task put_memrd_short
      (
       output       completed,
       inout [7:0]  data[],
       input [31:0] addr
       );
        logic [1:0] len;

        completed = 0;
        len      = data.size()-1;
        write_cmd({PUT_MEMRD32_SHORT, len});
        write_byte(addr[31:24]);
        write_byte(addr[23:16]);
        write_byte(addr[15:8]);
        write_byte(addr[7:0]);
        if (debug) begin
            $display("%m(%t): ADDR = %08x", $time, addr);
        end
        tar_phase();
        read_response();
        if (tb_byte_in == ACCEPT) begin
            completed = 1;
            read_data(data);
        end
        read_status();
    endtask // put_memrd_short
    
    task put_iowr_short
      (
       output completed,
       input [15:0] addr,
       input [7:0]  data[]
       );
        logic [1:0] len;

        len = data.size()-1;
        write_cmd({PUT_IOWR_SHORT, len});
        write_byte(addr[15:8]);
        write_byte(addr[7:0]);
        if (debug) begin
            $display("%m(%t): ADDR = %04x", $time, addr);
        end
        write_data(data);
        tar_phase();
        read_response();
        completed = tb_byte_in == ACCEPT;
        read_status();
    endtask // put_iowr_short
    
    task put_iord_short
      (
       output       completed,
       inout [7:0]  data[],
       input [15:0] addr
       );
        logic [1:0] len;

        completed = 0;
        len      = data.size()-1;
        write_cmd({PUT_IORD_SHORT, len});
        write_byte(addr[15:8]);
        write_byte(addr[7:0]);
        if (debug) begin
            $display("%m(%t): ADDR = %04x", $time, addr);
        end
        tar_phase();
        read_response();
        if (tb_byte_in == ACCEPT) begin
            completed = 1;
            read_data(data);
        end
        read_status();
    endtask // put_iord_short
    
    task get_vwire
      (
       output [7:0] return_data[]
       );
        int groups;
        int length;
        
        write_cmd(GET_VWIRE);
        tar_phase();
        read_response();
        if (tb_byte_in == ACCEPT) begin
            read_byte();
            groups = tb_byte_in;
            if (debug) begin
                $display("%m(%t): GROUPS = %1d", $time, groups);
            end

            length      = 2*(groups+1);
            return_data = new[length];
            read_data(return_data);
        end
            
        read_status();
    endtask // get_vwire

    task put_vwire
      (
       input [7:0] vw_group[]
       );
        write_cmd(PUT_VWIRE);
        write_put_vwire_header(vw_group);
        tar_phase();
        read_response();
        read_status();
    endtask // put_vwire

    task put_oob
      (
       input [7:0] data[]
       );
        write_cmd(PUT_OOB);
        write_cycletype(OOB_MESSAGE);
        write_taglength(4'd0, data.size());
        write_data(data);
        tar_phase();
        read_response();
        read_status();
    endtask // put_oob

    task get_oob
      (
       output [7:0] data[]
       );
        logic [11:0] length;

        write_cmd(GET_OOB);
        tar_phase();
        read_response();
        if (tb_byte_in == ACCEPT) begin
            read_oob_header(length);
            data = new[length];
            read_data(data);
        end

        read_status();
    endtask // get_oob

    task get_pc
      (
       output [7:0]  cycle_type,
       output [3:0]  tag,
       output [11:0] len,
       output [63:0] addr,
       output [7:0]  data[]
      );
        write_cmd(GET_PC);
        tar_phase();
        read_response();
        if (tb_byte_in == ACCEPT) begin
            read_get_pc_header(cycle_type, tag, len, addr);
            if (cycle_type[0]) begin
                data = new[len];
                read_data(data);
            end
        end
        read_status();
    endtask // get_pc

    task get_np
      (
       output [7:0]  cycle_type,
       output [3:0]  tag,
       output [11:0] len,
       output [63:0] address,
       output [7:0]  data[]
      );
        write_cmd(GET_NP);
        tar_phase();
        read_response();
        if (tb_byte_in == ACCEPT) begin
            read_get_np_header(cycle_type, tag, len, address);
            if (cycle_type[0]) begin
                data = new[len];
                read_data(data);
            end
        end
        read_status();
    endtask // get_np

    task write_memwr_header
      (
       input [3:0]  tag,
       input [7:0]  address[],
       input [7:0]  data[]
       );
        write_cycletype(address.size() > 4 ? MEMORY_WRITE_64 : MEMORY_WRITE_32);
        write_taglength(tag, data.size());
        write_address(address);
    endtask // write_memwr_header

    task write_address(input [7:0] address[]);
        for (int i = address.size()-1; i >= 0; i--) begin
            write_byte(address[i]);
            if (debug) begin
                $display("%m(%t): ADDRESS[%1d] = %02x", $time, i, address[i]);
            end
        end
    endtask

    task write_cycletype(input [7:0] cycle_type);
        write_byte(cycle_type);
        if (debug) begin
            $display("%m(%t): CYCLETYPE = %s", $time, eSPI_CycleTypeEnum'(cycle_type));
        end
    endtask // write_cycletype

    task read_cycletype(output [7:0] cycle_type);
        read_byte();
        cycle_type = tb_byte_in;
        if (debug) begin
            $display("%m(%t): CYCLETYPE = %s", $time, eSPI_CycleTypeEnum'(cycle_type));
        end
    endtask // write_cycletype

    task read_get_pc_header
      (
       output [7:0]  cycle_type,
       output [3:0]  tag,
       output [11:0] len,
       output [63:0] address
       );
        read_cycletype(cycle_type);
        read_taglength(tag, len);
        if (is_memory_access(cycle_type)) begin
            address = 0;
            if (MEMORY_READ_64 || MEMORY_WRITE_64) begin
                read_byte();
                address[63:56] = tb_byte_in;
                read_byte();
                address[55:48] = tb_byte_in;
                read_byte();
                address[47:40] = tb_byte_in;
                read_byte();
                address[39:32] = tb_byte_in;
            end
            read_byte();
            address[31:24] = tb_byte_in;
            read_byte();
            address[23:16] = tb_byte_in;
            read_byte();
            address[15:8] = tb_byte_in;
            read_byte();
            address[7:0] = tb_byte_in;
        end // if (is_memory_access(cycle_type))
    endtask // read_get_pc_header

    task read_taglength(output [3:0] tag, output [11:0] len);
        read_byte();
        tag       = tb_byte_in[7:4];
        len[11:8] = tb_byte_in[3:0];
        read_byte();
        len[7:0] = tb_byte_in;
        if (debug) begin
            $display("%m(%t): TAG = %1d, LENGTH = %1d", $time, tag, len);
        end
    endtask        

    function bit is_memory_access(logic [7:0] cycle_type);
        is_memory_access = 0;
        is_memory_access = is_memory_access || (cycle_type == MEMORY_READ_32);
        is_memory_access = is_memory_access || (cycle_type == MEMORY_READ_64);
        is_memory_access = is_memory_access || (cycle_type == MEMORY_WRITE_32);
        is_memory_access = is_memory_access || (cycle_type == MEMORY_WRITE_64);
    endfunction // is_memory_access

    task read_get_np_header
      (
       output [7:0]  cycle_type,
       output [3:0]  tag,
       output [11:0] len,
       output [63:0] address
       );
        read_cycletype(cycle_type);
        read_taglength(tag, len);
        if (is_memory_access(cycle_type)) begin
            address = 0;
            if (MEMORY_READ_64 || MEMORY_WRITE_64) begin
                read_byte();
                address[63:56] = tb_byte_in;
                read_byte();
                address[55:48] = tb_byte_in;
                read_byte();
                address[47:40] = tb_byte_in;
                read_byte();
                address[39:32] = tb_byte_in;
            end
            read_byte();
            address[31:24] = tb_byte_in;
            read_byte();
            address[23:16] = tb_byte_in;
            read_byte();
            address[15:8] = tb_byte_in;
            read_byte();
            address[7:0] = tb_byte_in;
        end // if (is_memory_access(cycle_type))
    endtask // read_get_np_header

    task write_memrd_header
      (
       input [3:0]  tag,
       input [11:0] len,
       input [7:0]  address[]
       );
        write_cycletype(address.size() > 4 ? MEMORY_READ_64 : MEMORY_READ_32);
        write_taglength(tag, len);
        write_address(address);
    endtask // write_memrd_header

    task write_taglength(input [3:0] tag, input [11:0] len);
        if (debug) begin
            $display("%m(%t): TAG = %1d, LENGTH = %1d", $time, tag, len);
        end
        write_byte({tag, len[11:8]});
        write_byte(len[7:0]);
    endtask        

    task read_memrd_completion
      (
       output [7:0]  cycle_type,
       output [3:0]  tag,
       output [11:0] len,
       output [7:0]  data[]
       );
        bit successful;
        bit with_data;

        read_cycletype(cycle_type);
        read_taglength(tag, len);
        successful = !(cycle_type[7:4] == 4'b0001 && !cycle_type[0]);
        with_data  = cycle_type[0];
        if (successful && with_data) begin
            data = new[len];
            read_data(data);
        end
    endtask // read_memrd_completion

    task write_set_config_header
      (
       input [11:0] address,
       input [31:0] data
       );
        if (verbose) begin
            $display("%m(%t): ADDRESS = %03x, DATA = %08x", $time, address, data);
        end

        write_byte( { 4'h0, address[11:8] } );
        if (debug) begin
            $display("%m(%t): HEADER[0] = %02x", $time, tb_byte_out );
        end

        write_byte( address[7:0] );
        if (debug) begin
            $display("%m(%t): HEADER[1] = %02x", $time, tb_byte_out );
        end

        write_byte( data[7:0] );
        if (debug) begin
            $display("%m(%t): DATA[0] = %02x", $time, tb_byte_out );
        end

        write_byte( data[15:8] );
        if (debug) begin
            $display("%m(%t): DATA[1] = %02x", $time, tb_byte_out );
        end
        
        write_byte( data[23:16] );
        if (debug) begin
            $display("%m(%t): DATA[2] = %02x", $time, tb_byte_out );
        end
        
        write_byte( data[31:24] );
        if (debug) begin
            $display("%m(%t): DATA[3] = %02x", $time, tb_byte_out );
        end
    endtask // write_set_config_header

    task write_get_config_header
      (
       input [11:0] address
       );

        write_byte( { 4'h0, address[11:8] } );
        if (debug || verbose) begin
            $display("%m(%t): HEADER[0] = %02x", $time, tb_byte_out);
        end

        write_byte( address[7:0] );
        if (debug || verbose) begin
            $display("%m(%t) HEADER[1] = %02x", $time, tb_byte_out);
        end
    endtask // write_get_config_header
        
    task write_put_vwire_header
      (
       input [7:0] vw_group[]
       );
        int groups;
        int length;

        length = vw_group.size();
        groups = length/2;
        write_byte(groups-1);
        if (debug) begin
            $display("%m(%t): GROUPS = %1d", $time, groups);
        end
        
        for (int i = 0; i < length; i++) begin
            write_byte(vw_group[i]);
            if (debug) begin
                $display("%m(%t): DATA = %02x", $time, tb_byte_out);
            end
        end
    endtask // write_put_vwire_header

    task read_oob_header
      (
       output [11:0] length
       );
        logic [7:0]  cycle_type;
        logic [3:0]  tag;

        read_cycletype(cycle_type);
        assert (tb_byte_in == OOB_MESSAGE) else begin
            $error("%m(%t): CYCLE TYPE: expected = %02x, actual = %02x",
                   $time, OOB_MESSAGE, tb_byte_in);
            if (stop_on_error) begin
                $stop;
            end
        end

        read_taglength(tag, length);
    endtask // read_oob_header

    task write_cmd
      (
       input [7:0] cmd
       );

        init_tx(cmd);
        write_byte(cmd);
        tb_posted = is_posted_command(cmd);
        if (debug || verbose) begin
            $display("%m(%t): CMD = %s", $time, eSPI_Command_Opcode_Enum'(cmd));
        end
    endtask // write_cmd

    function bit is_posted_command(logic [7:0] cmd);
        is_posted_command = 0;
        is_posted_command = is_posted_command || (cmd == GET_PC);
        is_posted_command = is_posted_command || (cmd == PUT_PC);
        is_posted_command = is_posted_command || (cmd == GET_NP);
        is_posted_command = is_posted_command || (cmd == PUT_VWIRE);
        is_posted_command = is_posted_command || (cmd == GET_VWIRE);
        is_posted_command = is_posted_command || (cmd == PUT_OOB);
        is_posted_command = is_posted_command || (cmd == GET_OOB);
        is_posted_command = is_posted_command || (cmd == GET_STATUS);
        is_posted_command = is_posted_command || (cmd == SET_CONFIGURATION);
        is_posted_command = is_posted_command || (cmd == GET_CONFIGURATION);
    endfunction // is_posted_command

    task write_data
      (
       input [7:0] data[]
       );
        for (int i = 0; i < data.size(); i++) begin
            write_byte(data[i]);
            if (debug) begin
                $display("%m(%t): DATA[%1d] = %02x", $time, i, data[i]);
            end
        end
    endtask // write_data

    task read_data
      (
       inout [7:0] data[]
       );
        for (int i = 0; i < data.size(); i++) begin
            read_byte();
            data[i] = tb_byte_in;
            if (debug) begin
                $display("%m(%t): DATA = %02x", $time, tb_byte_in);
            end
        end
    endtask // read_data

    task read_response();
        tb_ignore_waits = 1;
        read_byte;
        if (debug) begin
            $display("%m(%t): RESPONSE = %s", $time, eSPI_ResponseFieldEnumType'(tb_byte_in));
        end
        
        // While RESPONSE = WAIT_STATE... repeat
        while (tb_byte_in == WAIT_STATE) begin
            read_byte;
            if (debug) begin
                $display("%m(%t): RESPONSE = %s", $time, eSPI_ResponseFieldEnumType'(tb_byte_in));
            end
        end

        if (!debug && verbose) begin
            $display("%m(%t): RESPONSE = %s", $time, eSPI_ResponseFieldEnumType'(tb_byte_in));
        end

        if (!((tb_byte_in == ACCEPT) ||
              (!tb_posted && tb_byte_in == DEFER) ||
              (tb_byte_in == tb_response))) begin
            $error("%m(%t): RESPONSE: expected = %08b, actual = %08b",
                   $time, tb_response, tb_byte_in );
            if (stop_on_error) begin
                $stop;
            end
        end
    endtask // read_response

    task read_status();
        read_byte();
        espi_status[7:0] = tb_byte_in;
        if (debug || verbose) begin
            $display("%m(%t): STATUS[0] = %08b", $time, tb_byte_in );
        end

        read_byte();
        espi_status[15:8] = tb_byte_in;
        if (debug || verbose) begin
            $display("%m(%t): STATUS[1] = %08b", $time, tb_byte_in );
        end

        read_byte();
        if (debug) begin
            $display("%m(%t): CRC = %08b", $time, tb_byte_in );
        end

        if (tb_crc8 != 0) begin
            $error("%m(%t): CRC = %02x", $time, tb_crc8);
            if (stop_on_error) begin
                $stop();
            end
        end
        
        term_tx;
    endtask // read_status

    // Initiate Transaction
    task init_tx
      (
       input [7:0] command
       );
        clr_crc8;
        
        // Assert CS#
        #( TSHSL )         // Minimum hold
        espi_cs_n   <= 1'b0;
        
        // Set bit pointer to lsb of 1st bit set = 7, 6, or 4
        tb_bit_ptr  <= ( 3'd7 << tb_io_mode );
        
        // Pre-load COMMAND out
        tb_byte_out <= command;
        
        // Enable data out
        #( TSLCH - TDVCH - TCK / 2 )
        tb_dataout_en <= 1'b1;
        
        // Enable espi_clk
        #( TDVCH )
        // Wait for clock to be low to avoid a glitch
        wait (!wsclk);
        start_clock <= 1'b1;
    endtask // init_tx

    task write_byte
      (
       input [7:0] writebyte
       );
        
        // Load BYTE out
        tb_byte_out <= writebyte;
        
        // Write BYTE out - 7 clocks
        for ( tb_bit_ptr = ( 3'd7 << tb_io_mode );
              tb_bit_ptr > 3'd0;
              tb_bit_ptr = tb_bit_ptr - ( 3'b001 << tb_io_mode ) )
          begin
              @ ( negedge espi_clk )                          // Wait for falling edge espi_clk
                #( TCKL - TDVCH );
          end
        
        // +1 clock
        @ ( negedge espi_clk )                          // Wait for falling edge espi_clk
          #( TCKL - TDVCH );

        calc_crc8(writebyte);
    endtask // write_byte

    // Turn-Around Phase
    task tar_phase();
        if (tb_crc_error) begin
            tb_crc8 = ~tb_crc8;
        end
        
        write_byte(tb_crc8);
        if (debug) begin
            $display("%m(%t): CRC = %02x", $time, tb_byte_out);
        end

        if (verbose) begin
            $display("%m(%t): TAR", $time );
        end

        clr_crc8;
        
        // Master drives '1' 1st clock
        tb_byte_out <= 8'hff;
        
        // Master goes high-Z 2nd clock
        @ ( negedge espi_clk )
          #( TCKL - TDVCH )
        tb_dataout_en <= 1'b0;

        // Set bit pointer to lsb of 1st bit set = 7, 6, or 4
        tb_bit_ptr    <= 3'd7 << tb_io_mode;
        
        // Exit TAR
        @ ( posedge espi_clk )                          // Sample on rising edge espi_clk
          #( TCHDX );

        @ ( negedge espi_clk )                          // Sample on falling edge espi_clk
        #( TCHDX );
    endtask // tar_phase

    // Read Byte
    task read_byte();
        // Read BYTE in - 7 clocks
        for ( tb_bit_ptr = ( 3'd7 << tb_io_mode );
              tb_bit_ptr > 3'd0;
              tb_bit_ptr = tb_bit_ptr - ( 3'b001 << tb_io_mode ) )
          begin
              @ ( posedge espi_clk )                          // Sample on rising edge espi_clk
                #( TCHDX );
              @ ( negedge espi_clk )                          // Sample on falling edge espi_clk
              #( TCHDX );
          end
        
        // +1 clock
        @ ( posedge espi_clk )                          // Sample on rising edge espi_clk
          #( TCHDX );
        @ ( negedge espi_clk )                          // Sample on falling edge espi_clk
        #( TCHDX );

        if (!tb_ignore_waits || tb_byte_in != WAIT_STATE) begin
            tb_ignore_waits = 0;
            calc_crc8(tb_byte_in);
        end
    endtask // read_byte

    // Terminated Transaction
    task term_tx();
        // Disable espi_clk
        wait ( !espi_clk );
        start_clock <= 1'b0;
        
        // Master drives '1' after last clock
        tb_byte_out <= 8'hff;  
        
        // De-assert CS#
        #( TCLSH )             // Minimum setup
        espi_cs_n <= 1'b1;

        // Disable data out
        #( TSHQZ )
        tb_dataout_en <= 1'b0;
        
        #( TSHSL - TSHQZ );   // Minimum hold
    endtask // term_tx

    task calc_crc8
      (
       input       [7:0]   d
       );
        logic [7:0] c;
        logic [7:0] crc;

        c = tb_crc8;
        crc[0]  = d[7] ^ d[6] ^ d[0] ^ c[7] ^ c[6] ^ c[0];
        crc[1]  = d[6] ^ d[1] ^ d[0] ^ c[6] ^ c[1] ^ c[0];
        crc[2]  = d[6] ^ d[2] ^ d[1] ^ d[0] ^ c[6] ^ c[2] ^ c[1] ^ c[0];
        crc[3]  = d[7] ^ d[3] ^ d[2] ^ d[1] ^ c[7] ^ c[3] ^ c[2] ^ c[1];
        crc[4]  = d[4] ^ d[3] ^ d[2] ^ c[4] ^ c[3] ^ c[2];
        crc[5]  = d[5] ^ d[4] ^ d[3] ^ c[5] ^ c[4] ^ c[3];
        crc[6]  = d[6] ^ d[5] ^ d[4] ^ c[6] ^ c[5] ^ c[4];
        crc[7]  = d[7] ^ d[6] ^ d[5] ^ c[7] ^ c[6] ^ c[5]; 
        tb_crc8 = crc;
    endtask // calc_crc8

    // CRC-8 clear
    task clr_crc8();
        tb_crc8[7:0] <= 8'h00;
    endtask // clr_crc8

    task reset();
        tb_io_mode    = 2'b00;
        tb_op_freq    = 3'b000;
        tb_response   = ACCEPT;
        espi_cs_n     = 1'b1;
        espi_reset_n  = 1'b0;
        tb_dataout_en = 1'b0;
        tb_data_out   = 4'bzzzz;
        start_clock   = 1'b0;
        set_timing_params();
        #1000;

        espi_reset_n  = 1'b1;
    endtask // reset

    task set_timing_params;
        case (tb_op_freq)
          1: begin
              // 25 MHz
              TCK   = 40;
              TCKH  = 16;
              TCKL  = 16;
              TSLCH = 60;
              TCLSH = 40;
              TSHSL = 40;
              TDVCH = 10;
              TCHDX = 10;
              TCLQZ = 12;
              TCLQV = 15;
              TCLQX = 0;
              TSHQZ = 12;
              TSLAZ = 12;
              TSHAA = 12;
              TINIT = 1;
          end

          2: begin
              // 33 MHz
              TCK   = 30;
              TCKH  = 12;
              TCKL  = 12;
              TSLCH = 45;
              TCLSH = 30;
              TSHSL = 30;
              TDVCH = 7;
              TCHDX = 7;
              TCLQZ = 9;
              TCLQV = 10;
              TCLQX = 0;
              TSHQZ = 9;
              TSLAZ = 9;
              TSHAA = 9;
              TINIT = 1;
          end

          3: begin
              // 50 MHz
              TCK   = 20;
              TCKH  = 8;
              TCKL  = 8;
              TSLCH = 30;
              TCLSH = 20;
              TSHSL = 20;
              TDVCH = 3.500;
              TCHDX = 2.500;
              TCLQZ = 8;
              TCLQV = 8;
              TCLQX = 0;
              TSHQZ = 8;
              TSLAZ = 8;
              TSHAA = 8;
              TINIT = 1;
          end

          4: begin
              // 66 MHz
              TCK   = 15;
              TCKH  = 6;
              TCKL  = 6;
              TSLCH = 22;
              TCLSH = 15;
              TSHSL = 15;
              TDVCH = 3;
              TCHDX = 3;
              TCLQZ = 6;
              TCLQV = 6;
              TCLQX = 0;
              TSHQZ = 6;
              TSLAZ = 6;
              TSHAA = 6;
              TINIT = 1;
          end

          default: begin
              // 20 MHz
              TCK   = 50;
              TCKH  = 20;
              TCKL  = 20;
              TSLCH = 75;
              TCLSH = 50;
              TSHSL = 50;
              TDVCH = 12;
              TCHDX = 12;
              TCLQZ = 15;
              TCLQV = 20;
              TCLQX = 0;
              TSHQZ = 15;
              TSLAZ = 15;
              TSHAA = 15;
              TINIT = 1;
          end
        endcase
    endtask

endmodule // espi_master_bfm
